package hasthi.container;

import hasthi.common.HasthiError;
import hasthi.common.HasthiException;
import hasthi.common.Utils;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.impl.DefaultHttpClientConnection;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.xmlpull.v1.builder.XmlBuilderException;

public class HttpClient {
    protected static Log log = LogFactory.getLog(HasthiConstants.LOGGER_NAME);
    private HttpParams params;
    private HttpRequestExecutor httpexecutor;
    private BasicHttpProcessor httpproc;

    public HttpClient() {
        params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_0);
        HttpProtocolParams.setContentCharset(params, "UTF-8");
        HttpProtocolParams.setUserAgent(params, "HttpComponents/1.1");
        HttpProtocolParams.setUseExpectContinue(params, true);

        httpproc = new BasicHttpProcessor();
        // Required protocol interceptors
        httpproc.addInterceptor(new RequestContent());
        httpproc.addInterceptor(new RequestTargetHost());
        // Recommended protocol interceptors
        httpproc.addInterceptor(new RequestConnControl());
        httpproc.addInterceptor(new RequestUserAgent());
        httpproc.addInterceptor(new RequestExpectContinue());

        httpexecutor = new HttpRequestExecutor();

    }

    public XmlObject invokeXml(XmlObject xmlRequest, String soapAddress) throws HasthiException {
        HttpContext context = new BasicHttpContext(null);
        DefaultHttpClientConnection conn = new DefaultHttpClientConnection();
        try {
            URL url = new URL(soapAddress);

            HttpHost host = new HttpHost(url.getHost(), url.getPort());

            context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
            context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, host);

            if (!conn.isOpen()) {
                Socket socket = new Socket(host.getHostName(), host.getPort());
                conn.bind(socket, params);
            }
            
            String path = url.getPath();
            if(path == null || path.trim().length() == 0){
                path = "/";
            }
            BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("POST",path);
            request.setEntity(new XmlBeansHttpEntity(xmlRequest, false));

            context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
            request.setParams(params);
            httpexecutor.preProcess(request, httpproc, context);
            HttpResponse response = httpexecutor.execute(request, conn, context);
            response.setParams(params);
            httpexecutor.postProcess(response, httpproc, context);

            org.apache.http.StatusLine statusLine = response.getStatusLine();
            if (statusLine.getStatusCode() == HttpStatus.SC_ACCEPTED) {
                return null;
            } else if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
                return XmlObject.Factory.parse(response.getEntity().getContent());
            } else if (statusLine.getStatusCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
                throw new HasthiException("Http related exception (Internal Server Error, 500)"
                        + statusLine, FaultCode.ErrorAtDependentService);
            } else {
                String message = Utils.readFromStream(response.getEntity().getContent());
                throw new HasthiException("Unknown Status Code " + statusLine.getStatusCode() + " "
                        + message, FaultCode.ErrorAtClientWhileWsCall);
            }

        } catch (UnknownHostException e) {
            throw new HasthiException(e, FaultCode.ServiceNotReachable);
        } catch (IOException e) {
            throw new HasthiException(e, FaultCode.LocalError);
        } catch (HttpException e) {
            throw new HasthiException(e, FaultCode.ErrorAtClientWhileWsCall);
        } catch (XmlException e) {
            throw new HasthiException(e, FaultCode.ErrorAtDependentService);
        } finally {
            try {
                conn.close();
            } catch (IOException e) {
                log.error("Error while closing HTTP connection", e);
            }
        }

    }

    public static class XmlBeansHttpEntity implements HttpEntity {
        private XmlObject message;
        // We need to do this only with xsul as a client. Content length -1
        // means do chunking
        private byte[] buffer;
        private boolean chunked;

        public XmlBeansHttpEntity(XmlObject message, boolean chunked) {
            this.chunked = chunked;
            if (chunked) {
                this.message = message;
            } else {
                try {
                    ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();
                    message.save(bufferStream);
                    bufferStream.flush();
                    buffer = bufferStream.toByteArray();
                } catch (XmlBuilderException e) {
                    throw new HasthiError(e);
                } catch (IOException e) {
                    throw new HasthiError(e);
                }
            }
        }

        public void consumeContent() throws IOException {
            this.message = null;
            buffer = null;
        }

        public InputStream getContent() throws IOException, IllegalStateException {
            // TODO Auto-generated method stub
            return null;
        }

        public Header getContentEncoding() {
            return null;
            // return new BasicHeader(HTTP.CONTENT_ENCODING ,HTTP.UTF_8);
        }

        public long getContentLength() {
            if (chunked) {
                return -1;
            } else {
                return buffer.length;
            }
        }

        public Header getContentType() {
            // return new BasicHeader(HTTP.CONTENT_TYPE,"text/xml");
            return new BasicHeader(HTTP.CONTENT_TYPE, "text/xml;charset=utf-8");
        }

        public boolean isChunked() {
            return false;
        }

        public boolean isRepeatable() {
            return false;
        }

        public boolean isStreaming() {
            return true;
        }

        public void writeTo(OutputStream outstream) throws IOException {
            if (chunked) {
                message.save(outstream);
            } else {
                outstream.write(buffer);
                outstream.flush();
            }

        }
    }
}
